package com.tianji.learning.service.impl;

import com.tianji.common.autoconfigure.mq.RabbitMqHelper;
import com.tianji.common.constants.MqConstants;
import com.tianji.common.exceptions.BizIllegalException;
import com.tianji.common.utils.*;
import com.tianji.learning.domain.po.SignInMessage;
import com.tianji.learning.domain.vo.SignResultVO;
import com.tianji.learning.service.ISignRecordService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.connection.BitFieldSubCommands;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.List;

import static com.tianji.learning.domain.constants.RedisConstants.SIGN_RECORD_KEY_PREFIX;

@Api(tags = "签到相关接口")
@Service
@Slf4j
@RequiredArgsConstructor
public class SignRecordServiceImpl implements ISignRecordService {
    private final StringRedisTemplate stringRedisTemplate;
    private final RabbitMqHelper rabbitMq;
    /**
     * 用redis实现签到功能
     *
     * @return
     */
    @Override
    @ApiOperation("签到功能接口")
    public SignResultVO addSignRecords() {
        //1.存入redis
        //1.1.获取用户id
        Long user = UserContext.getUser();
        //1.2.组装key
        String key =  getkey(user);
        //1.3.计算偏移量
        int days = LocalDateTime.now().getDayOfMonth();
        int offset = days - 1;

        //优化：定期删除redis签到数据
        //1.4.0 判断是否存在key
        Long keyCount = stringRedisTemplate.countExistingKeys(Collections.singleton(key));
         boolean keyExists = false;
        if(ObjectUtils.isNotEmpty(keyCount) && keyCount > 0){
            keyExists = true;
        }

        //1.4.存入redis的bitmap
        Boolean flag = stringRedisTemplate.opsForValue().setBit(key, offset, true);
        if (BooleanUtils.isTrue(flag)) {
            throw new BizIllegalException("今日已签到");
        }
        //1.4.2 如果key不存在则设置过期时间
         if(!keyExists){
            LocalDateTime beginTime = LocalDateTime.now();  //  ==》  2024-12-01 00:00:00
            LocalDateTime endTime = DateUtils.getMonthBeginTime(LocalDate.now().plusMonths(1));
            Duration between = DateUtils.between(beginTime, endTime);
            stringRedisTemplate.expire(key,between);
        }

        //2.计算连续天数
        int signDays =  countDays(key,days);

        //3.计算签到得分
        int rewardPoints = 0;
        switch (signDays){
            case 7: rewardPoints = 10;break;
            case 14: rewardPoints = 20;break;
            case 28: rewardPoints = 40;break;
            default: break;
        }

        //4.增加积分
        try {
            rabbitMq.send(
                     MqConstants.Exchange.LEARNING_EXCHANGE,
                    MqConstants.Key.SIGN_IN,
                    SignInMessage.of(UserContext.getUser(), rewardPoints + 1)
            );
        } catch (Exception e) {
            log.error("签到积分消息发送失败", e);
        }
        //5.封装vo
        SignResultVO signResultVO = new SignResultVO();
        signResultVO.setSignDays(signDays);
        signResultVO.setRewardPoints(rewardPoints);
        return signResultVO;
    }

    /**
     * 查询签到结果
     *
     * @return
     */
    @Override
    public Byte[] querySignRecords() {
        //1.查询redis整型数
        String key = getkey(UserContext.getUser());
        int days = LocalDate.now().getDayOfMonth();
        List<Long> longs = stringRedisTemplate.opsForValue().bitField(key, BitFieldSubCommands.create()
                .get(BitFieldSubCommands.BitFieldType.unsigned(days)).valueAt(0));
        if(ObjectUtils.isEmpty(longs)){
            return new Byte[0];
        }
        int num = longs.get(0).intValue();

        //2.通过位运算不断在bite数组中添加
        int i = days - 1; //签到总次数 = 当前天数-1
        Byte[] bytes = new Byte[days];
        while (i >=0){
            bytes[i--] = (byte) (num & 1);
            num = num >>> 1;
        }

        //3.返回结果
        return bytes;
    }

    /**
     * 计算本月连续签到总数
     * @param key
     * @param days
     * @return
     */
    private int countDays(String key, int days) {
        //1.查询redis返回整形数
        List<Long> longs = stringRedisTemplate.opsForValue().bitField(key, BitFieldSubCommands.create()
                .get(BitFieldSubCommands.BitFieldType.unsigned(days)).valueAt(0));
        if (CollUtils.isEmpty(longs)){
            return 0;
        }

        int num =  longs.get(0).intValue();

        //2.开始循环计数
        int count = 0;
        while ( (num & 1) == 1 ){
            count++;
            num = num >>> 1;
        }
        return count;
    }

    private String getkey(Long user) {
        //获取当前时间
        LocalDate now = LocalDate.now();
        //组装key
        return SIGN_RECORD_KEY_PREFIX + user + now.format(DateUtils.SIGN_DATE_SUFFIX_FORMATTER);
    }
}
